////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.controls
{

import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.text.TextLineMetrics;
import flash.ui.Keyboard;
import flash.utils.Timer;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.EdgeMetrics;
import mx.core.IDataRenderer;
import mx.core.IFlexDisplayObject;
import mx.core.IInvalidating;
import mx.core.IUIComponent;
import mx.core.UIComponent;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.events.FlexEvent;
import mx.managers.IFocusManagerComponent;
import mx.skins.ProgrammaticSkin;
import mx.skins.RectangularBorder;
import mx.styles.ISimpleStyleClient;

use namespace mx_internal;

//--------------------------------------
//  Events
//--------------------------------------

/**
 *  Dispatched when the user presses the Button control.
 *  If the <code>autoRepeat</code> property is <code>true</code>,
 *  this event is dispatched repeatedly as long as the button stays down.
 *
 *  @eventType mx.events.FlexEvent.BUTTON_DOWN
 */
[Event(name="buttonDown", type="mx.events.FlexEvent")]

/**
 *  Dispatched when the <code>selected</code> property 
 *  changes for a toggle Button control. A toggle Button control means that the 
 *  <code>toggle</code> property is set to <code>true</code>. 
 * 
 *  For the CheckBox 
 *  and RadioButton controls, this event is dispatched when the <code>selected</code> 
 *  property changes.
 *
 *  @eventType flash.events.Event.CHANGE
 */
[Event(name="change", type="flash.events.Event")]

/**
 *  Dispatched when the <code>data</code> property changes.
 *
 *  <p>When you use a component as an item renderer,
 *  the <code>data</code> property contains the data to display.
 *  You can listen for this event and update the component
 *  when the <code>data</code> property changes.</p>
 * 
 *  @eventType mx.events.FlexEvent.DATA_CHANGE
 */
[Event(name="dataChange", type="mx.events.FlexEvent")]

//--------------------------------------
//  Styles
//--------------------------------------

include "../styles/metadata/FocusStyles.as"
include "../styles/metadata/LeadingStyle.as"
include "../styles/metadata/PaddingStyles.as"
include "../styles/metadata/SkinStyles.as"
include "../styles/metadata/TextStyles.as"

/**
 *  Gap between the label and icon, when the <code>labelPlacement</code> property
 *  is set to <code>left</code> or <code>right</code>.
 * 
 *  @default 2
 */
[Style(name="horizontalGap", type="Number", format="Length", inherit="no")]

/**
 *  Number of pixels between the container's bottom border
 *  and the bottom of its content area.
 *  
 *  @default 0 
 */
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]

/**
 *  Number of pixels between the container's top border
 *  and the top of its content area.
 *  
 *  @default 0
 */
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]

/**
 *  Number of milliseconds to wait after the first <code>buttonDown</code>
 *  event before repeating <code>buttonDown</code> events at each 
 *  <code>repeatInterval</code>.
 * 
 *  @default 500
 */
[Style(name="repeatDelay", type="Number", format="Time", inherit="no")]

/**
 *  Number of milliseconds between <code>buttonDown</code> events
 *  if the user presses and holds the mouse on a button.
 *  
 *  @default 35
 */
[Style(name="repeatInterval", type="Number", format="Time", inherit="no")]

/**
 *  Text color of the label as the user moves the mouse pointer over the button.
 *  
 *  @default 0x2B333C
 */
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]

/**
 *  Text color of the label as the user presses it.
 *  
 *  @default 0x000000
 */
[Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")]

/**
 *  Gap between the button's label and icon when the <code>labelPlacement</code>
 *  property is set to <code>"top"</code> or <code>"bottom"</code>.
 * 
 *  @default 2
 */
[Style(name="verticalGap", type="Number", format="Length", inherit="no")]

//--------------------------------------
//  Skins
//--------------------------------------

/**
 *  Name of the class to use as the skin for the background and border
 *  when the button is not selected and the mouse is not over the control.
 *  
 *  @default "mx.skins.halo.ButtonSkin"
 */
[Style(name="upSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the skin for the background and border
 *  when the button is not selected and the mouse is over the control.
 *  
 *  @default "mx.skins.halo.ButtonSkin" 
 */
[Style(name="overSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the skin for the background and border
 *  when the button is not selected and the mouse button is down.
 *  
 *  @default "mx.skins.halo.ButtonSkin"
 */
[Style(name="downSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the skin for the background and border
 *  when the button is not selected and is disabled.
 * 
 *  @default "mx.skins.halo.ButtonSkin"
 */
[Style(name="disabledSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the skin for the background and border
 *  when a toggle button is selected and the mouse is not over the control.
 * 
 *  @default "mx.skins.halo.ButtonSkin" 
 */
[Style(name="selectedUpSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the skin for the background and border
 *  when a toggle button is selected and the mouse is over the control.
 *  
 *  @default "mx.skins.halo.ButtonSkin"
 */
[Style(name="selectedOverSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the skin for the background and border
 *  when a toggle button is selected and the mouse button is down.
 *  
 *  @default "mx.skins.halo.ButtonSkin"
 */
[Style(name="selectedDownSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the skin for the background and border
 *  when a toggle button is selected and disabled.
 * 
 *  @default "mx.skins.halo.ButtonSkin"
 */
[Style(name="selectedDisabledSkin", type="Class", inherit="no")]

/**
 *  Name of the class to use as the default icon. 
 *  Setting any other icon style overrides this setting.
 *  
 *  @default null 
 */
[Style(name="icon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon when a toggle button is not 
 *  selected and the mouse is not over the button.
 * 
 *  @default null 
 */
[Style(name="upIcon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon when the button is not 
 *  selected and the mouse is over the control.
 * 
 *  @default null 
 */
[Style(name="overIcon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon when the button is not 
 *  selected and the mouse button is down.
 * 
 *  @default null 
 */
[Style(name="downIcon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon
 *  when the button is not disabled.
 * 
 *  @default null 
 */
[Style(name="disabledIcon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon
 *  when the button is selected and the mouse button is up.
 * 
 *  @default null 
 */
[Style(name="selectedUpIcon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon
 *  when the button is selected and the mouse is over the control.
 * 
 *  @default null 
 */
[Style(name="selectedOverIcon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon
 *  when the button is selected and the mouse button is down.
 * 
 *  @default null 
 */
[Style(name="selectedDownIcon", type="Class", inherit="no")]

/**
 *  Name of the class to use as the icon
 *  when the button is selected and disabled.
 * 
 *  @default null 
 */
[Style(name="selectedDisabledIcon", type="Class", inherit="no")]

//--------------------------------------
//  Other metadata
//--------------------------------------

[AccessibilityClass(implementation="mx.accessibility.ButtonAccImpl")]

[DefaultBindingProperty(source="selected", destination="label")]

[DefaultTriggerEvent("click")]

[IconFile("Button.png")]

/**
 *  The Button control is a commonly used rectangular button.
 *  Button controls look like they can be pressed.
 *  They can have a text label, an icon, or both on their face.
 *
 *  <p>Buttons typically use event listeners to perform an action 
 *  when the user selects the control. When a user clicks the mouse 
 *  on a Button control, and the Button control is enabled, 
 *  it dispatches a <code>click</code> event and a <code>buttonDown</code> event. 
 *  A button always dispatches events such as the <code>mouseMove</code>, 
 *  <code>mouseOver</code>, <code>mouseOut</code>, <code>rollOver</code>, 
 *  <code>rollOut</code>, <code>mouseDown</code>, and 
 *  <code>mouseUp</code> events whether enabled or disabled.</p>
 *
 *  <p>You can customize the look of a Button control
 *  and change its functionality from a push button to a toggle button.
 *  You can change the button appearance by using a skin
 *  for each of the button's states.</p>
 *
 *  @mxml
 *
 *  <p>The <code>&lt;mx:Button&gt;</code> tag inherits all the tag attributes
 *  of its superclass, and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;mx:Button
 *    <b>Properties</b>
 *    autoRepeat="false|true"
 *    emphasized="false|true"
 *    label=""
 *    labelPlacement="right|left|bottom|top"
 *    selected="false|true"
 *    selectedField="null"
 *    stickyHighlighting="false|true"
 *    toggle="false|true"<br></br>
 *    <b>Styles</b>
 *    borderColor="0xAAB3B3"
 *    color="0x0B333C"
 *    cornerRadius="4"
 *    disabledColor="0xAAB3B3"
 *    disabledIcon="null"
 *    disabledSkin="mx.skins.halo.ButtonSkin"
 *    downIcon="null"
 *    downSkin="mx.skins.halo.ButtonSkin"
 *    fillAlphas="[0.6, 0.4]"
 *    fillColors="[0xE6EEEE, 0xFFFFFF]"
 *    focusAlpha="0.5"
 *    focusRoundedCorners"tl tr bl br"
 *    fontAntiAliasType="advanced"
 *    fontFamily="Verdana"
 *    fontGridFitType="pixel"
 *    fontSharpness="0"
 *    fontSize="10"
 *    fontStyle="normal|italic"
 *    fontThickness="0"
 *    fontWeight="normal|bold"
 *    highlightAlphas="[0.3, 0.0]"
 *    horizontalGap="2"
 *    icon="null"
 *    leading="2"
 *    overIcon="<i>No default</i>"
 *    overSkin="mx.skins.halo.ButtonSkin"
 *    paddingBottom="0"
 *    paddingLeft="0"
 *    paddingRight="0"
 *    paddingTop="0"
 *    repeatDelay="500"
 *    repeatInterval="35"
 *    selectedDisabledIcon="null"
 *    selectedDisabledSkin="mx.skins.halo.ButtonSkin"
 *    selectedDownIcon="<i>No default</i>"
 *    selectedDownSkin="mx.skins.halo.ButtonSkin"
 *    selectedOverIcon="null"
 *    selectedOverSkin="mx.skins.halo.ButtonSkin"
 *    selectedUpIcon="null"
 *    selectedUpSkin="mx.skins.halo.ButtonSkin"
 *    textAlign="center|left|right"
 *    textDecoration="none|underline"
 *    textIndent="0"
 *    textRollOverColor="0x2B333C"
 *    textSelectedColor="0x000000"
 *    upIcon="null"
 *    upSkin="mx.skins.halo.ButtonSkin"
 *    verticalGap="2"<br></br>
 *    <b>Events</b>
 *    buttonDown="<i>No default</i>"
 *    change="<i>No default</i>"
 *    dataChange="<i>No default</i>"
 *  /&gt;
 *  </pre>
 *
 *  @includeExample examples/ButtonExample.mxml
 */
public class Button extends UIComponent
	   implements IDataRenderer, IDropInListItemRenderer,
	   IFocusManagerComponent, IListItemRenderer
{
    include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class mixins
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *  Placeholder for mixin by ButtonAccImpl.
	 */
	mx_internal static var createAccessibilityImplementation:Function;

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

    /**
	 *  Constructor.
	 */
    public function Button()
    {
		super();

		// DisplayObjectContainer properties.
		// Setting mouseChildren to false ensure that mouse events
		// are dispatched from the Button itself,
		// not from its skins, icons, or TextField.
		// One reason for doing this is that if you press the mouse button
		// while over the TextField and release the mouse button while over
		// a skin or icon, we want the player to dispatch a "click" event.
		// Another is that if mouseChildren were true and someone uses
		// Sprites rather than Shapes for the skins or icons,
		// then we we wouldn't get a click because the current skin or icon
		// changes between the mouseDown and the mouseUp.
		// (This doesn't happen even when mouseChildren is true if the skins
		// and icons are Shapes, because Shapes never dispatch mouse events;
		// they are dispatched from the Button in this case.)
		mouseChildren = false;

		// Register for player events.
		addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
		addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
		addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
		addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
		addEventListener(MouseEvent.CLICK, clickHandler);
    }

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *  Skins for the various states (falseUp, trueOver, etc.)
	 *  are created just-in-time as they are needed.
	 *  Each skin is a child Sprite of this Button.
	 *  Each skin has a name property indicating which skin it is;
	 *  for example, the instance of the class specified by the falseUpSkin
	 *  style has the name "falseUpSkin" and can be found using
	 *  getChildByName(). Note that there is no falseUpSkin property
	 *  of Button containing a reference to this skin instance.
	 *  This array contains references to all skins that have been created,
	 *  for looping over them; without this array we wouldn't know
	 *  which of the children are the skins.
	 *  New skins are created and added to this array in viewSkin().
	 */
	private var skins:Array /* of Sprite */ = [];

	/**
	 *  @private
	 *  A reference to the current skin.
	 *  Set by viewSkin().
	 */
	mx_internal var currentSkin:IFlexDisplayObject;

	/**
	 *  @private
	 *  Skins for the various states (falseUp, trueOver, etc.)
	 *  are created just-in-time as they are needed.
	 *  Each icon is a child Sprite of this Button.
	 *  Each icon has a name property indicating which icon it is;
	 *  for example, the instance of the class specified by the falseUpIcon
	 *  style has the name "falseUpIcon" and can be found using
	 *  getChildByName(). Note that there is no falseUpIcon property
	 *  of Button containing a reference to this icon instance.
	 *  This array contains references to all icons that have been created,
	 *  for looping over them; without this array we wouldn't know
	 *  which of the children are the icons.
	 *  New icons are created and added to this array in viewIcon().
	 */
	private var icons:Array /* of Sprite */ = [];

	/**
	 *  @private
	 *  A reference to the current icon.
	 *  Set by viewIcon().
	 */
	mx_internal var currentIcon:IFlexDisplayObject;

	/**
	 *  @private
	 *	Timer for doing auto-repeat.
	 */
	private var autoRepeatTimer:Timer;

	/**
	 *  @private
	 *  Number used to offset the label and/or icon
	 *  when button is pressed.
	 */
	mx_internal var buttonOffset:Number = 0;

    /**
	 *  @private
	 */
	mx_internal var centerContent:Boolean = true;

    /**
	 *  @private
	 */
    mx_internal var extraSpacing:Number = 10 + 10;

	/**
	 *  @private
	 */
	private var styleChangedFlag:Boolean = true;

	/**
	 *  @private
	 *  The measured width of the first skin loaded.
	 */
	private var skinMeasuredWidth:Number;

	/**
	 *  @private
	 *  The measured height of the first skin loaded.
	 */
	private var skinMeasuredHeight:Number;

	/**
	 *  @private
	 *  The value of the unscaledWidth parameter during the most recent
	 *  call to updateDisplayList
	 */
	private var oldUnscaledWidth:Number;

	/**
	 *  @private
	 *  Flags that will block default data/listData behavior
	 */
	private var selectedSet:Boolean;
	private var labelSet:Boolean;

	/**
	 *  @private
	 *  Skin names.
	 *  Allows subclasses to re-define the skin property names.
	 */
	mx_internal var upSkinName:String = "upSkin";
	mx_internal var overSkinName:String = "overSkin";
	mx_internal var downSkinName:String = "downSkin";
	mx_internal var disabledSkinName:String = "disabledSkin";
	mx_internal var selectedUpSkinName:String = "selectedUpSkin";
	mx_internal var selectedOverSkinName:String = "selectedOverSkin";
	mx_internal var selectedDownSkinName:String = "selectedDownSkin";
	mx_internal var selectedDisabledSkinName:String = "selectedDisabledSkin";

	/**
	 *  @private
	 *  Icon names.
	 *  Allows subclasses to re-define the icon property names.
	 */
	mx_internal var iconName:String = "icon";
	mx_internal var upIconName:String = "upIcon";
	mx_internal var overIconName:String = "overIcon";
	mx_internal var downIconName:String = "downIcon";
	mx_internal var disabledIconName:String = "disabledIcon";
	mx_internal var selectedUpIconName:String = "selectedUpIcon";
	mx_internal var selectedOverIconName:String = "selectedOverIcon";
	mx_internal var selectedDownIconName:String = "selectedDownIcon";
	mx_internal var selectedDisabledIconName:String = "selectedDisabledIcon";

	//--------------------------------------------------------------------------
	//
	//  Overridden properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  baselinePosition
	//----------------------------------

    /**
	 *  @private
	 */
	override public function get baselinePosition():Number
    {
		var t:String = label;
		if (!t)
			t = "Wj";

		// If we're dirty, force a relayout here
		// so that our internal text field is positioned first;
		// otherwise we get a stale value.
		validateNow();

		// if we havent specified a label and the placement is either top
		// or bottom, we will set the baseline to less that half the height.
		if (!label &&
			(labelPlacement == ButtonLabelPlacement.TOP ||
			 labelPlacement == ButtonLabelPlacement.BOTTOM))
		{
			var lineMetrics:TextLineMetrics = measureText(t);
			return (measuredHeight - lineMetrics.height) / 2 +
				   lineMetrics.ascent;
		}

		return textField.y + measureText(t).ascent;

    }

	//----------------------------------
	//  enabled
	//----------------------------------

	/**
	 *  @private
	 */
	private var enabledChanged:Boolean = false;

    [Inspectable(category="General", enumeration="true,false", defaultValue="true")]

	/**
	 *  @private
	 *  This is called whenever the enabled state changes.
	 */
    override public function set enabled(value:Boolean):void
    {
    	if (super.enabled == value)
			return;

		super.enabled = value;
		enabledChanged = true;

		invalidateProperties();
		invalidateDisplayList();
    }

	//----------------------------------
	//  textField
	//----------------------------------

    /**
     *  The internal UITextField object that renders the label of this Button.
     * 
     *  @default null 
	 */
    protected var textField:UITextField;

	//----------------------------------
	//  toolTip
	//----------------------------------

	/**
	 *  @private
	 */
	private var toolTipSet:Boolean = false;

    [Inspectable(category="General", defaultValue="null")]

	/**
     *  @private
     */
    override public function set toolTip(value:String):void
    {
        super.toolTip = value;

        if (value)
		{
        	toolTipSet = true;
		}
        else
        {
        	toolTipSet = false;
        	invalidateDisplayList();
        }
    }

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  autoRepeat
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the autoRepeat property.
	 */
	private var _autoRepeat:Boolean = false;

	[Inspectable(defaultValue="false")]

	/**
	 *  Specifies whether to dispatch repeated <code>buttonDown</code>
	 *  events if the user holds down the mouse button.
	 *
	 *  @default false
	 */
	public function get autoRepeat():Boolean
	{
		return _autoRepeat;
	}

	/**
	 *  @private
	 */
	public function set autoRepeat(value:Boolean):void
	{
		_autoRepeat = value;

		if (value)
		{
			// Create a Timer object for driving the autorepeat.
			// The duration gets set in mouseDownHandler and reset
			// in autoRepeatTimer_timerDelayHandler, because
			// there is a delay before the first autorepeat
			// and then a possibly different interval
			// between subsequent ones.
			autoRepeatTimer = new Timer(1);
		}
		else
		{
			autoRepeatTimer = null;
		}
	}

	//----------------------------------
	//  data
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the data property;
	 */
	private var _data:Object;

	[Bindable("dataChange")]
	[Inspectable(environment="none")]

    /**
     *  The <code>data</code> property lets you pass a value
     *  to the component when you use it as an item renderer or item editor.
     *  You typically use data binding to bind a field of the <code>data</code>
     *  property to a property of this component.
     *
     *  <p>When you use the control as a drop-in item renderer or drop-in
	 *  item editor, Flex automatically writes the current value of the item
	 *  to the <code>selected</code> property of this control.</p>
     *
     *  <p>You do not set this property in MXML.</p>
	 *
	 *  @default null
     *  @see mx.core.IDataRenderer
     */
	public function get data():Object
	{
		if (!_data)
			_data = {};

		return _data;
	}

    /**
     *  @private
     */
	public function set data(value:Object):void
	{
		var newSelected:*;
		var newLabel:*;

		_data = value;

		if (_listData && _listData is DataGridListData)
		{
			newSelected = _data[DataGridListData(_listData).dataField];

			newLabel = "";
		}
		else if (_listData)
		{
			if (selectedField)
				newSelected = _data[selectedField];

			newLabel = _listData.label;
		}
		else
		{
			newSelected = _data;
		}

		if (newSelected !== undefined && !selectedSet)
		{
			selected = newSelected as Boolean;
			selectedSet = false;
		}
		if (newLabel !== undefined && !labelSet)
		{
			label = newLabel;
			labelSet = false;
		}

		dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
	}

	//----------------------------------
	//  emphasized
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the emphasized property.
	 */
	mx_internal var _emphasized:Boolean = false;

	[Inspectable(category="General", defaultValue="false")]

	/**
	 *  Draws a thick border around the Button control
	 *  when the control is in its up state if <code>emphasized</code>
	 *  is set to <code>true</code>. 
	 *
	 *  @default false
	 */
	public function get emphasized():Boolean
	{
		return _emphasized;
	}

	/**
	 *  @private
	 */
	public function set emphasized(value:Boolean):void
	{
		_emphasized = value;

		changeSkins();

		invalidateDisplayList();
	}

	//----------------------------------
	//  label
	//----------------------------------

    /**
	 *  @private
	 *  Storage for label property.
	 */
    private var _label:String = "";

    /**
	 *  @private
	 */
    private var labelChanged:Boolean = false;

    [Bindable("labelChanged")]
    [Inspectable(category="General", defaultValue="")]

	/**
	 *  Text to appear on the Button control.
	 *
	 *  <p>If the label is wider than the Button control,
	 *  the label is truncated and terminated by an ellipsis (...).
	 *  The full label displays as a tooltip
	 *  when the user moves the mouse over the Button control.
	 *  If you have also set a tooltip by using the <code>tooltip</code>
	 *  property, the tooltip is displayed rather than the label text.</p>
	 *
	 *  @default ""
	 */
    public function get label():String
    {
        return _label;
    }

 	/**
	 *  @private
	 */
    public function set label(value:String):void
    {
		labelSet = true;

    	if (_label != value)
    	{
			_label = value;
			labelChanged = true;

			invalidateSize();
			invalidateDisplayList();

			dispatchEvent(new Event("labelChanged"));
		}
    }

	//----------------------------------
	//  labelPlacement
	//----------------------------------

    /**
	 *  @private
	 *  Storage for labelPlacement property.
	 */
    mx_internal var _labelPlacement:String = ButtonLabelPlacement.RIGHT;

    [Bindable("labelPlacementChanged")]
    [Inspectable(category="General", enumeration="left,right,top,bottom", defaultValue="right")]

	/**
	 *  Orientation of the label in relation to a specified icon.
     *  Valid MXML values are <code>right</code>, <code>left</code>,
     *  <code>bottom</code>, and <code>top</code>.
	 *
	 *  <p>In ActionScript, you can use the following constants
	 *  to set this property:
	 *  <code>ButtonLabelPlacement.RIGHT</code>,
	 *  <code>ButtonLabelPlacement.LEFT</code>,
	 *  <code>ButtonLabelPlacement.BOTTOM</code>, and
	 *  <code>ButtonLabelPlacement.TOP</code>.</p>
	 *
	 *  @default ButtonLabelPlacement.RIGHT
	 */
    public function get labelPlacement():String
    {
        return _labelPlacement;
    }

	/**
	 *  @private
	 */
    public function set labelPlacement(value:String):void
    {
        _labelPlacement = value;

        invalidateSize();
		invalidateDisplayList();

		dispatchEvent(new Event("labelPlacementChanged"));
    }

	//-----------------------------------
	//  listData
	//-----------------------------------

	/**
	 *  @private
	 *  Storage for the listData property.
	 */
	private var _listData:BaseListData;

	[Bindable("dataChange")]
	[Inspectable(environment="none")]

	/**
     *  When a component is used as a drop-in item renderer or drop-in
	 *  item editor, Flex initializes the <code>listData</code> property
	 *  of the component with the appropriate data from the list control.
	 *  The component can then use the <code>listData</code> property
	 *  to initialize the <code>data</code> property
     *  of the drop-in item renderer or drop-in item editor.
     *
     *  <p>You do not set this property in MXML or ActionScript;
	 *  Flex sets it when the component is used as a drop-in item renderer
	 *  or drop-in item editor.</p>
     *
     *  @default null
	 *  @see mx.controls.listClasses.IDropInListItemRenderer
	 */
	public function get listData():BaseListData
	{
		return _listData;
	}

    /**
     *  @private
     */
	public function set listData(value:BaseListData):void
	{
		_listData = value;
	}

	//----------------------------------
	//  phase
	//----------------------------------

	/**
	 *  @private
	 *  Mouse and focus events set this to
	 *  ButtonPhase.UP, ButtonPhase.OVER, or ButtonPhase.DOWN.
	 */
	private var _phase:String = ButtonPhase.UP;

	/**
	 *  @private
	 */
	mx_internal function get phase():String
	{
		return _phase;
	}

	/**
	 *  @private
	 */
	mx_internal function set phase(value:String):void
	{
		_phase = value;

		invalidateSize();
		invalidateDisplayList();
	}

	//----------------------------------
	//  selected
	//----------------------------------

	/**
	 *  @private
	 *  Storage for selected property.
	 */
	mx_internal var _selected:Boolean = false;

	[Bindable("click")]
	[Bindable("valueCommit")]
	[Inspectable(category="General", defaultValue="false")]

	/**
	 *  Indicates whether a toggle button is toggled
	 *  on (<code>true</code>) or off (<code>false</code>).
	 *  This property can be set only if the <code>toggle</code> property
	 *  is set to <code>true</code>.
	 *
	 *  <p>For a CheckBox control, indicates whether the box
	 *  is displaying a check mark. For a RadioButton control, 
	 *  indicates whether the control is selected.</p>
     *
	 *  <p>The user can change this property by clicking the control,
	 *  but you can also set the property programmatically.</p>
	 *
     *  <p>If the <code>toggle</code> property is set to <code>true</code>, 
     *  changing this property also dispatches a <code>change</code> event.</p>
	 *
	 *  @default false
	 */
	public function get selected():Boolean
	{
		return _selected;
	}

	/**
	 *  @private
	 */
	public function set selected(value:Boolean):void
	{
		selectedSet = true;
		if (_selected != value)
		{
			_selected = value;

			invalidateDisplayList();
	
			if (toggle)
				dispatchEvent(new Event(Event.CHANGE));

			dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
		}
	}

	//----------------------------------
	//  selectedField
	//----------------------------------

	/**
	 *  The name of the field in the <code>data</code> property which specifies
	 *  the value of the Button control's <code>selected</code> property. 
	 *  You can set this property when you use the Button control in an item renderer.
	 *  The default value is null, which means that the Button control does 
	 *  not set its selected state based on a property in the <code>data</code> property.
	 *
	 *  @default null
	 */
	public var selectedField:String = null;

	//----------------------------------
	//  stickyHighlighting
	//----------------------------------

	/**
	 *  If <code>false</code>, the Button displays its down skin
	 *  when the user presses it but changes to its over skin when
	 *  the user drags the mouse off of it.
	 *  If <code>true</code>, the Button displays its down skin
	 *  when the user presses it, and continues to display this skin
	 *  when the user drags the mouse off of it.
	 *
	 *  <p>Button subclasses, such as the SliderThumb and ScrollThumb classes
	 *  or the up and down arrows of a ScrollBar, set 
	 *  this property to <code>true</code>.</p>
	 *
	 *  @default false
	 */
	public var stickyHighlighting:Boolean = false;

	//----------------------------------
	//  toggle
	//----------------------------------

	/**
	 *  @private
	 *  Storage for toggle property.
	 */
	mx_internal var _toggle:Boolean = false;

	/**
	 *  @private
	 */
	mx_internal var toggleChanged:Boolean = false;

	[Bindable("toggleChanged")]
	[Inspectable(category="General", defaultValue="false")]

	/**
	 *  Controls whether a Button is in a toggle state or not. 
	 * 
	 *  If <code>true</code>, clicking the button toggles it
	 *  between a selected and an unselected state.
	 *  You can get or set this state programmatically
	 *  by using the <code>selected</code> property.
	 *
	 *  If <code>false</code>, the button does not stay pressed
	 *  after the user releases it.
	 *  In this case, its <code>selected</code> property
	 *  is always <code>false</code>.
	 *  Buttons like this are used for performing actions.
	 *
	 *  When <code>toggle</code> is set to <code>false</code>,
	 *  <code>selected</code> is forced to <code>false</code>
	 *  because only toggle buttons can be selected.
	 *
	 *  @default false
	 */
	public function get toggle():Boolean
	{
		return _toggle;
	}

	/**
	 *  @private
	 */
	public function set toggle(value:Boolean):void
	{
		_toggle = value;
		toggleChanged = true;

		invalidateProperties();
		invalidateDisplayList();

		dispatchEvent(new Event("toggleChanged"));
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function initializeAccessibility():void
	{
		if (Button.createAccessibilityImplementation != null)
			Button.createAccessibilityImplementation(this);
	}

	/**
	 *  @private
	 */
    override protected function createChildren():void
    {
		super.createChildren();

		// Create a UITextField to display the label.
		if (!textField)
		{
			textField = new UITextField();
			textField.styleName = this;
			addChild(textField);
		}
    }

	/**
	 *  @private
	 */
    override protected function commitProperties():void
    {
    	super.commitProperties();

		if (!initialized)
		{
            viewSkin();
            viewIcon();
		}

		if (enabledChanged)
		{
			textField.enabled = enabled;
			enabledChanged = false;
		}

		if (toggleChanged)
		{
			// If the button is no longer toggleable,
			// deselect it.
			if (!toggle)
				selected = false;
			toggleChanged = false;
		}
    }

	/**
	 *  @private
	 */
    override protected function measure():void
    {
		super.measure();

		var textWidth:Number = 0;
		var textHeight:Number = 0;

		if (label)
		{
			var lineMetrics:TextLineMetrics = measureText(label);
			textWidth = lineMetrics.width;
	        textHeight = lineMetrics.height;

	        textWidth += (getStyle("paddingLeft") +
						  getStyle("paddingRight") +
						  getStyle("textIndent"));
			textHeight += (getStyle("paddingTop") +
						   getStyle("paddingBottom"));
		}

		// If the current skin defines a borderMetrics property,
		// then use it; otherwise, use a default value.
		var bm:EdgeMetrics;
		try
		{
			bm = currentSkin["borderMetrics"];
		}
		catch(e:Error)
		{
			bm = new EdgeMetrics(3, 3, 3, 3);
		}
	
		var tempCurrentIcon:IFlexDisplayObject = getCurrentIcon();	
        var iconWidth:Number = tempCurrentIcon ? tempCurrentIcon.width : 0;
        var iconHeight:Number = tempCurrentIcon ? tempCurrentIcon.height : 0;

        var w:Number = 0;
        var h:Number = 0;

        if (labelPlacement == ButtonLabelPlacement.LEFT ||
			labelPlacement == ButtonLabelPlacement.RIGHT)
        {
            w = textWidth + iconWidth;
            if (iconWidth != 0)
            	w += (getStyle("horizontalGap") - 2);
            h = Math.max(textHeight, iconHeight + 6);
        }
        else
        {
            w = Math.max(textWidth, iconWidth);
            h = textHeight + iconHeight;
            if (iconHeight != 0)
            	h += getStyle("verticalGap");
        }

        if (bm)
        {
            w += bm.left + bm.right;
            h += bm.top + bm.bottom
        }

        // Pad with additional spacing, but only if we have a label.
        if (label && label.length != 0)
            w += extraSpacing;
        else
            w += 6;

		// Use the larger of the measured sizes and the skin's preferred sizes.
		// Each skin should override measure() with their measuredWidth
		// and measuredHeight.
		if (currentSkin && (isNaN(skinMeasuredWidth) || isNaN(skinMeasuredHeight)))
		{
			skinMeasuredWidth = currentSkin.measuredWidth;
			skinMeasuredHeight = currentSkin.measuredHeight;
		}

		if (!isNaN(skinMeasuredWidth))
			w = Math.max(skinMeasuredWidth, w);

		if (!isNaN(skinMeasuredHeight))
			h = Math.max(skinMeasuredHeight, h);

        measuredMinWidth = measuredWidth = w;
        measuredMinHeight = measuredHeight = h;
    }

	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
    {
		super.updateDisplayList(unscaledWidth, unscaledHeight);

		// Set each skin's size to the layout size of this Button.
		var n:int = skins.length;
		for (var i:int = 0; i < n; i++)
		{
			var skin:IFlexDisplayObject = IFlexDisplayObject(skins[i]);
			skin.setActualSize(unscaledWidth, unscaledHeight);
		}

		// Show the appropriate skin and icon, based on whether this
		// Button is enabled or disabled, whether it is selected
		// or unselected, and how it is currently interacting
		// with the mouse (i.e., the up/over/down phase).
		viewSkin();
		viewIcon();

		if (currentIcon && currentIcon is IUIComponent)
			IUIComponent(currentIcon).enabled = enabled;

		layoutContents(unscaledWidth, unscaledHeight,
					   phase == ButtonPhase.DOWN);

		// If our width changed, reset the label text to get it to fit.
		if (oldUnscaledWidth > unscaledWidth ||
			textField.text != label ||
			labelChanged ||
			styleChangedFlag)
		{
			textField.text = label;
			var truncated:Boolean = textField.truncateToFit();
			if (!toolTipSet)
			{
				if (truncated)
					super.toolTip = label;
				else
					super.toolTip = null;
			}

			styleChangedFlag = false;
            labelChanged = false;
		}

		oldUnscaledWidth = unscaledWidth;
    }

	/**
	 *  @private
	 */
	override public function styleChanged(styleProp:String):void
	{
		styleChangedFlag = true;

		super.styleChanged(styleProp);

		// Check for skin/icon changes here.
		// We could only throw out any skins that change,
		// but since dynamic re-skinning is uncommon, we'll take
		// the simpler approach of throwing out all skins.
		if (!styleProp || styleProp == "styleName")
		{
			// All style props have changed, so dump skins and icons.
			changeSkins();
			changeIcons();
		}
		else if (styleProp.toLowerCase().indexOf("skin") != -1)
		{
			changeSkins();
		}
		else if (styleProp.toLowerCase().indexOf("icon") != -1)
		{
			changeIcons();
			invalidateSize();
		}
	}

	/**
	 *  @private
	 */
	override protected function adjustFocusRect(
									object:DisplayObject = null):void
	{
		// If we don't have a skin, show focus around the icon.
        super.adjustFocusRect(!currentSkin ? DisplayObject(currentIcon) : this);
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 *  Displays one of the eight possible skins,
	 *  creating it if it doesn't already exist.
	 */
    mx_internal function viewSkin():void
    {
		// Determine which skin to display, based on whether this
		// Button is enabled or disabled, whether it is
		// selected or unselected, and how it is currently interacting
		// with the mouse (i.e., the up/over/down state).
		var skinName:String;

		if (!enabled)
			skinName = selected ? selectedDisabledSkinName : disabledSkinName;

		else if (phase == ButtonPhase.UP)
			skinName = selected ? selectedUpSkinName : upSkinName;

		else if (phase == ButtonPhase.OVER)
			skinName = selected ? selectedOverSkinName : overSkinName;

		else if (phase == ButtonPhase.DOWN)
			skinName = selected ? selectedDownSkinName : downSkinName;

		viewSkinForPhase(skinName);
    }

	/**
	 *  @private
	 *  Displays one of the several possible skins,
	 *  depending on the skinName and creating
	 *  it if it doesn't already exist.
	 */
	mx_internal function viewSkinForPhase(skinName:String):void
    {
		// Has this skin already been created?
		var newSkin:IFlexDisplayObject =
			IFlexDisplayObject(getChildByName(skinName));

		// If not, create it.
		if (!newSkin)
		{
			var newSkinClass:Class = Class(getStyle(skinName));
			if (newSkinClass)
			{
				newSkin = IFlexDisplayObject(new newSkinClass());

				// Set its name so that we can find it in the future
				// using getChildByName().
				newSkin.name = skinName;

				// Make the getStyle() calls in ButtonSkin find the styles
				// for this Button.
				var styleableSkin:ISimpleStyleClient = newSkin as ISimpleStyleClient;
				if (styleableSkin)
					styleableSkin.styleName = this;

				addChild(DisplayObject(newSkin));

				// Make the skin the proper size for this Button.
				// This will cause to skin to be drawn by drawHaloRect()
				// in ButtonSkin.
				newSkin.setActualSize(unscaledWidth, unscaledHeight);

				// If the skin is programmatic, and we've already been
				// initialized, update it now to avoid flicker.
				if (newSkin is IInvalidating && initialized)
				{
					IInvalidating(newSkin).validateNow();
				}
				else if (newSkin is ProgrammaticSkin && initialized)
				{
					ProgrammaticSkin(newSkin).validateDisplayList()
				}

				// Keep track of all skin children that have been created.
				skins.push(newSkin);
			}
		}

		// Hide the old skin.
		if (currentSkin)
			currentSkin.visible = false;

		// Keep track of which skin is current.
		currentSkin = newSkin;

		// Show the new skin.
		if (currentSkin)
			currentSkin.visible = true;

		var labelColor:Number;

        if (enabled)
        {
			if (phase == ButtonPhase.OVER)
        		labelColor = textField.getStyle("textRollOverColor");
        	else if (phase == ButtonPhase.DOWN)
        		labelColor = textField.getStyle("textSelectedColor");
        	else
        		labelColor = textField.getStyle("color");

        	textField.setColor(labelColor);
        }
	}

	/**
	 *  @private
	 *  Gets the currentIconName (string) based on the Button's phase.
	 */
	mx_internal function getCurrentIconName():String
	{
		var tempIconName:String;

		if (!enabled)
		{
			tempIconName = selected ?
						   selectedDisabledIconName :
						   disabledIconName;
		}
		else if (phase == ButtonPhase.UP)
		{
			tempIconName = selected ? selectedUpIconName : upIconName;
		}
		else if (phase == ButtonPhase.OVER)
		{
			tempIconName = selected ? selectedOverIconName : overIconName;
		}
		else if (phase == ButtonPhase.DOWN)
		{
			tempIconName = selected ? selectedDownIconName : downIconName;
		}
		
		return tempIconName;
	}


	/**
	 *  @private
	 *  gets the currentIcon based on the button.phase 
	 */
	mx_internal function getCurrentIcon():IFlexDisplayObject
	{
		// Determine which icon will get displayed, based on whether this
		// Button is enabled or disabled, whether it is
		// selected or unselected, and how it is currently interacting
		// with the mouse (i.e., the up/over/down state).

		var tempIconName:String = getCurrentIconName();	

		if (!tempIconName)
			return null;

	 	// Has this icon already been created?
		var newIcon:IFlexDisplayObject =
			IFlexDisplayObject(getChildByName(tempIconName));

		// If not, create it.
		if (newIcon == null)
		{
			//get specific icon style
			var newIconClass:Class = Class(getStyle(tempIconName));
			//default to the more general icon style
			if (newIconClass == null)
				newIconClass = Class(getStyle(iconName));

			if (newIconClass != null)
			{
				newIcon = IFlexDisplayObject(new newIconClass());

				// Set its name so that we can find it in the future
				// using getChildByName().
				newIcon.name = tempIconName;

				if (newIcon is ISimpleStyleClient)
					ISimpleStyleClient(newIcon).styleName = this;

				addChild(DisplayObject(newIcon));

				// Keep track of all skin children that have been created.
				icons.push(newIcon);
			}
		}

		return newIcon;
	}	

	/**
	 *  @private
	 *  Displays one of the eight possible icons,
	 *  creating it if it doesn't already exist.
	 */
	mx_internal function viewIcon():void
	{
		// Determine which icon to display, based on whether this
		// Button is enabled or disabled, whether it is
		// selected or unselected, and how it is currently interacting
		// with the mouse (i.e., the up/over/down state).
		var tempIconName:String = getCurrentIconName();

		viewIconForPhase(tempIconName);
	}

	/**
	 *  @private
	 *  Displays one of the several possible icons,
	 *  depending on the iconName and creating it if it
	 *  doesn't already exist.
	 */
	mx_internal function viewIconForPhase(tempIconName:String):void
	{
		// Has this icon already been created?
		var newIcon:IFlexDisplayObject =
			IFlexDisplayObject(getChildByName(tempIconName));

		// If not, create it.
		if (newIcon == null)
		{
			//get specific icon style
			var newIconClass:Class = Class(getStyle(tempIconName));
			//default to the more general icon style
			if (newIconClass == null)
				newIconClass = Class(getStyle(iconName));

			if (newIconClass != null)
			{
				newIcon = IFlexDisplayObject(new newIconClass());

				// Set its name so that we can find it in the future
				// using getChildByName().
				newIcon.name = tempIconName;

				if (newIcon is ISimpleStyleClient)
					ISimpleStyleClient(newIcon).styleName = this;

				addChild(DisplayObject(newIcon));

				// Keep track of all skin children that have been created.
				icons.push(newIcon);
			}
		}

		// Hide the old icon.
		if (currentIcon != null)
			currentIcon.visible = false;

		// Keep track of which skin is current.
		currentIcon = newIcon;

		// Show the new skin.
		if (currentIcon != null)
			currentIcon.visible = true;
	}

	/**
	 *  @private
	 *  Controls the layout of the icon and the label within the button.
	 *  Notice that layout hinges on a variable, centerContent, which is
	 *  set to true in Button, but false in CheckBox and RadioButton.
	 *  When centerContent is false, the text/icon are always left-aligned.
	 *  When centerContent is true, the text/icon are aligned
	 *  based on the textAlign style setting.
	 */
    mx_internal function layoutContents(unscaledWidth:Number,
									    unscaledHeight:Number,
									    offset:Boolean):void
    {
		var labelWidth:Number = 0;
        var labelHeight:Number = 0;

		var labelX:Number = 0;
        var labelY:Number = 0;

		var iconWidth:Number = 0;
        var iconHeight:Number = 0;

		var iconX:Number = 0;
		var iconY:Number = 0;

		var horizontalGap:Number = 2;
        var verticalGap:Number = 2;

		var paddingLeft:Number = getStyle("paddingLeft");
		var paddingRight:Number = getStyle("paddingRight");
		var paddingTop:Number = getStyle("paddingTop");
		var paddingBottom:Number = getStyle("paddingBottom");

		var textWidth:Number = 0;
		var textHeight:Number = 0;

		var lineMetrics:TextLineMetrics;

		if (label)
		{
			lineMetrics = measureText(label);
			if (lineMetrics.width > 0)
			{
				textWidth = paddingLeft + paddingRight +
							getStyle("textIndent") +  lineMetrics.width;
			}
	        textHeight = lineMetrics.height;
		}
		else
		{
			lineMetrics = measureText("Wj");
			textHeight = lineMetrics.height;
		}

        var n:Number = offset ? buttonOffset : 0;

        var textAlign:String = getStyle("textAlign");

        var bm:EdgeMetrics = currentSkin &&
							 currentSkin is RectangularBorder ?
							 RectangularBorder(currentSkin).borderMetrics :
							 null;

        var viewWidth:Number = unscaledWidth;
        var viewHeight:Number = unscaledHeight - paddingTop - paddingBottom;

		if (bm)
        {
            viewWidth -= bm.left + bm.right;
            viewHeight -= bm.top + bm.bottom;
        }

        if (currentIcon)
		{
            iconWidth = currentIcon.width;
            iconHeight = currentIcon.height;
        }

        if (labelPlacement == ButtonLabelPlacement.LEFT ||
			labelPlacement == ButtonLabelPlacement.RIGHT)
        {
        	horizontalGap = getStyle("horizontalGap");

			if (iconWidth == 0 || textWidth == 0)
        		horizontalGap = 0;

            if (textWidth > 0)
            {
                textField.width = labelWidth = Math.max(viewWidth - iconWidth - horizontalGap -
					paddingLeft - paddingRight, 0);
            }
            else
            {
                textField.width = labelWidth = 0;
            }
            textField.height = labelHeight =
				Math.min(viewHeight + 2, textHeight + UITextField.TEXT_HEIGHT_PADDING);

            if (labelPlacement == ButtonLabelPlacement.RIGHT)
            {
                labelX = iconWidth + horizontalGap;

				if (centerContent)
				{
                	if (textAlign == "left")
					{
                		labelX += paddingLeft;
					}
                	else if (textAlign == "right")
					{
                		labelX += (viewWidth - labelWidth -
								   iconWidth - horizontalGap - paddingLeft);
					}
                	else // "center" -- default value
					{
						var disp:Number = (viewWidth - labelWidth -
								   iconWidth - horizontalGap) / 2;
                    	labelX += Math.max(disp, paddingLeft);
					}
                }

				iconX = labelX - (iconWidth + horizontalGap);

                if (!centerContent)
                    labelX += paddingLeft;
            }
            else
            {
                labelX = viewWidth - labelWidth - iconWidth - horizontalGap -
                		 paddingRight;

				if (centerContent)
				{
                	if (textAlign == "left")
                		labelX = 2;
                	else if (textAlign == "right")
                		labelX -= 1;
                	else if (labelX > 0) // "center" -- default value
                    	labelX = labelX / 2;
                }

				iconX  = labelX + labelWidth + horizontalGap;
            }

            iconY  = labelY = 0;

			if (centerContent)
			{
                iconY  = Math.round((viewHeight - iconHeight) / 2) +
					paddingTop;
                labelY = Math.round((viewHeight - labelHeight) / 2) +
					paddingTop;
            }
            else
			{
                labelY += Math.max(0, (viewHeight - labelHeight) / 2) +
					paddingTop;
                iconY += Math.max(0, (viewHeight - iconHeight) / 2 - 1) +
					paddingTop;
            }
        }
        else
        {
        	verticalGap = getStyle("verticalGap");

			if (iconHeight == 0 || textHeight == 0)
        		verticalGap = 0;

            if (textWidth > 0)
            {
                textField.width = labelWidth =
					Math.min(viewWidth, textWidth + UITextField.TEXT_WIDTH_PADDING);
                textField.height = labelHeight =
					Math.min(viewHeight - iconHeight + 1, textHeight + 5);
            }
            else
            {
                textField.width = labelWidth = 0;
                textField.height = labelHeight = 0;
            }

            labelX = (viewWidth - labelWidth) / 2;

            iconX = (viewWidth - iconWidth) / 2;

            if (labelPlacement == ButtonLabelPlacement.TOP)
            {
                labelY = viewHeight - labelHeight - iconHeight - verticalGap;

				if (centerContent && labelY > 0)
                    labelY = labelY / 2;

				labelY += paddingTop;

				iconY = labelY + labelHeight + verticalGap - 3;
            }
            else
            {
                labelY = iconHeight + verticalGap + paddingTop;

				if (centerContent)
				{
                    labelY += (viewHeight - labelHeight -
							   iconHeight - verticalGap) / 2 + 1;
				}

				iconY = labelY - iconHeight - verticalGap + 3;
            }

        }
        var buffX:Number = n;
        var buffY:Number = n;

		if (bm)
        {
            buffX += bm.left;
            buffY += bm.top;
        }

        textField.x = labelX + buffX;
        textField.y = labelY + buffY;

		if (currentIcon)
		{
	        iconX += buffX;
	        iconY += buffY;

			currentIcon.x = Math.round(iconX);
			currentIcon.y = Math.round(iconY);
		}

		// The skins and icons get created on demand as the user interacts
		// with the Button, and as they are created they become the
		// frontmost child.
		// Here we ensure that the textField is the frontmost child,
		// with the current icon behind it and the current skin behind that.
		// Any other skins and icons are left behind these three,
		// with arbitrary layering.
		if (currentSkin)
			setChildIndex(DisplayObject(currentSkin), numChildren - 1);
		if (currentIcon)
			setChildIndex(DisplayObject(currentIcon), numChildren - 1);
		if (textField)
			setChildIndex(textField, numChildren - 1);
    }

	/**
	 *  @private
	 */
    mx_internal function changeSkins():void
    {
        var n:int = skins.length;
		for (var i:int = 0; i < n; i++)
		{
			removeChild(skins[i]);
		}
		skins = [];
    }

    /**
     *  @private
     */
    mx_internal function changeIcons():void
    {
        var n:int = icons.length;
		for (var i:int = 0; i < n; i++)
		{
			removeChild(icons[i]);
		}
		icons = [];
    }

    /**
     *  @private
     */
	mx_internal function buttonPressed():void
	{
		phase = ButtonPhase.DOWN;
		
		dispatchEvent(new FlexEvent(FlexEvent.BUTTON_DOWN));

		if (autoRepeat)
		{
			autoRepeatTimer.delay = getStyle("repeatDelay");
			autoRepeatTimer.addEventListener(
				TimerEvent.TIMER, autoRepeatTimer_timerDelayHandler);
			autoRepeatTimer.start();
		}
	}

    /**
     *  @private
     */
	mx_internal function buttonReleased():void
	{
		// Remove the handlers that were added in mouseDownHandler().
		systemManager.removeEventListener(
			MouseEvent.MOUSE_UP, systemManager_mouseUpHandler, true);
		systemManager.stage.removeEventListener(
			Event.MOUSE_LEAVE, stage_mouseLeaveHandler);
		
		if (autoRepeatTimer)
		{
			autoRepeatTimer.removeEventListener(
				TimerEvent.TIMER, autoRepeatTimer_timerDelayHandler);
			autoRepeatTimer.removeEventListener(
				TimerEvent.TIMER, autoRepeatTimer_timerHandler);
			autoRepeatTimer.reset();
		}
	}

	/**
	 *  @private
	 *  Some other components which use a Button as an internal
	 *  subcomponent need access to its UITextField, but can't access the
	 *  textField var because it is protected and therefore available
	 *  only to subclasses.
	 */
	mx_internal function getTextField():UITextField
	{
		return textField;
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden event handlers: UIComponent
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function focusOutHandler(event:FocusEvent):void
	{
		super.focusOutHandler(event);

		// Most of the time the system sends a rollout, but there are
		// situations where the mouse is over something else
		// that you don't get one so we force one here.
		if (phase != ButtonPhase.UP)
			phase = ButtonPhase.UP;
	}

	/**
	 *  @private
	 */
	override protected function keyDownHandler(event:KeyboardEvent):void
	{
		if (!enabled)
			return;

		if (event.keyCode == Keyboard.SPACE)
			buttonPressed();
	}

	/**
	 *  @private
	 */
	override protected function keyUpHandler(event:KeyboardEvent):void
	{
		if (!enabled)
			return;

		if (event.keyCode == Keyboard.SPACE)
		{
			buttonReleased();

			if (phase == ButtonPhase.DOWN)
				dispatchEvent(new MouseEvent(MouseEvent.CLICK));
			phase = ButtonPhase.UP;
		}
	}

	//--------------------------------------------------------------------------
	//
	//  Event handlers
	//
	//--------------------------------------------------------------------------

	/*

	Mouse interaction sequences that Button must handle:

	All start with Button in "up" phase, mouse outside Button,
	and mouse button up.

	Normal click:
		roll over Button -> "over" phase
		mouse down on Button -> "down" phase, dispatch "buttonDown"
		mouse up while over Button -> "over" phase, dispatch "click"
		roll out of Button -> "up" phase

	Click canceled:
		roll over Button -> "over" phase
		mouse down on Button -> "down" phase, dispatch "buttonDown"
		roll out of Button -> "over" phase
		maybe roll over and out of other objects -> dispatch events from them
		maybe roll off the stage, or off and back on
		mouse up while out of Button -> "up" phase
		if mouseup was over another Button, it goes into "over" phase

	Click resumed:
		roll over Button -> "over" phase
		mouse down on Button -> "down" phase, dispatch "buttonDown"
		roll out of Button -> "over" phase
		maybe roll over and out of other objects -> dispatch events from them
		roll over Button -> "down" phase
		maybe roll off the stage, or off and back on
		maybe repeat last four steps
		mouse up while over Button -> "over" phase, dispatch "click"
		roll out of Button -> "up" phase

	Drag over and out
		mouse down while out of Button
		roll over Button -> stay in "up" phase
		roll out of Button -> stay in "up" phase

	Drag over and up
		mouse down while out of Button
		roll over Button -> stay in "up" phase
		mouse up while over Button -> "over" phase
		continue with step 2 of first three sequences above

	*/

	/**
	 *  The default handler for the <code>MouseEvent.ROLL_OVER</code> event.
	 *
	 *  @param The event object.
	 */
	protected function rollOverHandler(event:MouseEvent):void
	{
		
		// Note that we don't prevent the propagation of rollOver
		// from a disabled Button.
		// Developers may want to detect this low-level event.

		if (phase == ButtonPhase.UP)
		{
			if (event.buttonDown)
				return;

			phase = ButtonPhase.OVER;
			
			// Force a "render" event, which will cause updateDisplayList()
			// to show the appropriate skin for the new phase.
			event.updateAfterEvent();
		}

		else if (phase == ButtonPhase.OVER)
		{
			phase = ButtonPhase.DOWN;
			
			// Force a "render" event, which will cause updateDisplayList()
			// to show the appropriate skin for the new phase.
			event.updateAfterEvent();
			
			// The mouse is back over the Button and the Button is down again,
			// so resume auto-repeating.
			if (autoRepeatTimer)
				autoRepeatTimer.start();
		}
	}

	/**
	 *  The default handler for the <code>MouseEvent.ROLL_OUT</code> event.
	 *
	 *  @param The event object.
	 */
	protected function rollOutHandler(event:MouseEvent):void
	{

		// Note that we don't prevent the propagation of rollOut
		// from a disabled Button.
		// Developers may want to detect this low-level event.

		if (phase == ButtonPhase.OVER)
		{
			phase = ButtonPhase.UP;
			
			// Force a "render" event, which will cause updateDisplayList()
			// to show the appropriate skin for the new phase.
			event.updateAfterEvent();
		}

		else if (phase == ButtonPhase.DOWN && !stickyHighlighting)
		{
			phase = ButtonPhase.OVER;
			
			// Force a "render" event, which will cause updateDisplayList()
			// to show the appropriate skin for the new phase.
			event.updateAfterEvent();

			// If the Button no longer looks "down", it shouldn't auto-repeat.
			if (autoRepeatTimer)
				autoRepeatTimer.stop();
		}
	}

	/**
	 *  The default handler for the <code>MouseEvent.MOUSE_DOWN</code> event.
	 *
	 *  @param The event object.
	 */
	protected function mouseDownHandler(event:MouseEvent):void
	{
		if (!enabled)
			return;

		// Note that we don't prevent the propagation of mouseDown
		// from a disabled Button.
		// Developers may want to detect this low-level event.

		// In case the user drags out of the Button and then releases
		// the mouse button, we need to get the mouseUp.
		// To accomplish this, we temporarily place a capture-phase
		// mouseUp handler on the SystemManager.
		// We also place a mouseLeave handler on the stage
		// in case the user drags off the stage and releases the mouse.
		// These handlers are removed in buttonReleased().
		systemManager.addEventListener(
			MouseEvent.MOUSE_UP, systemManager_mouseUpHandler, true);
		systemManager.stage.addEventListener(
			Event.MOUSE_LEAVE, stage_mouseLeaveHandler);

		buttonPressed();

		// Force a "render" event, which will cause updateDisplayList()
		// to show the appropriate skin for the new phase.
		event.updateAfterEvent();
	}

	/**
	 *  The default handler for the <code>MouseEvent.MOUSE_UP</code> event.
	 *
	 *  @param The event object.
	 */
	protected function mouseUpHandler(event:MouseEvent):void
	{
		if (!enabled)
			return;

		// Note that we don't prevent the propagation of mouseUp
		// from a disabled Button.
		// Developers may want to detect this low-level event.

		phase = ButtonPhase.OVER;
		buttonReleased();

		// Force a "render" event, which will cause updateDisplayList()
		// to show the appropriate skin for the new phase.
		event.updateAfterEvent();
	}

	/**
	 *  The default handler for the <code>MouseEvent.CLICK</code> event.
	 *
	 *  @param The event object.
	 */
	protected function clickHandler(event:MouseEvent):void
	{
		if (!enabled)
		{
			// Prevent the propagation of click from a disabled Button.
			// This is conceptually a higher-level event and
			// developers will expect their click handlers not to fire
			// if the Button is disabled.
			event.stopImmediatePropagation();
			return;
		}

		if (toggle)
			selected = !selected;
	}

	/**
	 *  @private
	 *  This method is called when the user has pressed the Button
	 *  and then released the mouse button anywhere.
	 *  It's purpose is to get the mouseUp event when the user has
	 *  dragged out of the Button before releasing.
	 *  However, it gets an inside mouseUp as well;
	 *  we have to check for this case becuase mouseHandler()
	 *  already deals with it..
	 */
	private function systemManager_mouseUpHandler(event:MouseEvent):void
	{
		// If the mouse button was released over the Button,
		// mouseUpHandler() will handle it, so do nothing. 
		if (contains(DisplayObject(event.target)))
			return;

		phase = ButtonPhase.UP;
		buttonReleased();

		// Force a "render" event, which will cause updateDisplayList()
		// to show the appropriate skin for the new phase.
		event.updateAfterEvent();
	}

	/**
	 *  @private
	 *  This method is called when the user has pressed the Button,
	 *  dragged of the stage, and released the mouse button.
	 */
	private function stage_mouseLeaveHandler(event:Event):void
	{
		phase = ButtonPhase.UP;
		buttonReleased();
	}

	/**
	 *  @private
	 */
	private function autoRepeatTimer_timerDelayHandler(event:Event):void
	{
		if (!enabled)
			return;

		dispatchEvent(new FlexEvent(FlexEvent.BUTTON_DOWN));

		if (autoRepeat)
		{
			autoRepeatTimer.reset();
			autoRepeatTimer.removeEventListener(
				TimerEvent.TIMER, autoRepeatTimer_timerDelayHandler);
			autoRepeatTimer.delay = getStyle("repeatInterval");
			autoRepeatTimer.addEventListener(
				TimerEvent.TIMER, autoRepeatTimer_timerHandler);
			autoRepeatTimer.start();
		}
	}

	/**
	 *  @private
	 */
	private function autoRepeatTimer_timerHandler(event:Event):void
	{
		if (!enabled)
			return;

		dispatchEvent(new FlexEvent(FlexEvent.BUTTON_DOWN));
	}
}

}
